<?php
/**
 * FastApp.
 * 8/28/24
 * @Author deepsea159
 * @Contact telegram:deepsea159
 */

namespace App\Exchange\Api;


use Hyperf\Contract\OnOpenInterface;
use Hyperf\Contract\OnRequestInterface;
use Hyperf\Di\Annotation\Inject;
use Hyperf\Redis\Redis;
use Hyperf\Validation\ValidatorFactory;
use Hyperf\WebSocketServer\Sender;

class WebSocketServer extends \Hyperf\WebSocketServer\Server implements OnRequestInterface, OnOpenInterface
{
    #[Inject]
    public ValidatorFactory $validatorFactory;
    #[Inject]
    public Redis $redis;

    public function onMessage($server, $frame): void
    {
        if (str_contains($frame->data, 'ping') || !str_contains($frame->data, 'event')) return;
        $data = json_decode($frame->data, true);
        if (!$data) return;
        if ($data['event'] == 'subscribe') {
            //{"event":"subscribe","app_source":"h5","type":"trade","symbol":"BTCUSDT"}
            if ($err = $this->paramValidator($data, ['type' => 'required'], false)) {
                self::sendMsg($frame->fd, 'param_error', $data, 400, $err);
                return;
            }
            $interval = ['1m', '5m', '15m', '30m', '1h', '4h', '12h', '1d', '1w', '1M'];
            $type = $data['type'];//trade contract
            if (empty($data['symbol'])) {
                $this->redis->sAdd("ex:$type", $frame->fd);
                return;
            }
            if ($type == 'trade') $interval[] = '1s';
            $prefix = $type == 'trade' ? 'kline_' : 'ckline_';
            foreach ($interval as $v) {
                $k = $prefix . strtolower($data['symbol']) . '_' . $v;
                $lineData = [];
                foreach ($this->redis->lRange('ex:' . $k, 0, -1) as $item) {
                    $lineData[] = json_decode($item, true);
                }
                self::sendMsg($frame->fd, $k . '_init', $lineData);
            }
            $scene = $data['app_source'];
            $uid = 0;
            if (!empty($data['token'])) {
                try {
                    $token = $data['token'];
                    $uid = user($scene)->getUserInfo($token)['id'];
                    $this->redis->hSet('u:' . $uid, "ex:$type:" . $data['symbol'], $frame->fd);
                } catch (\Throwable) {
                }
            }
            $this->redis->set('ex:' . $frame->fd, "$uid,$type," . strtolower($data['symbol']));
            $this->redis->sAdd("ex:$type:" . strtolower($data['symbol']), $frame->fd);
        }
    }

    public function onClose($server, int $fd, int $reactorId): void
    {
        $key = 'ex:' . $fd;
        if ($s = $this->redis->get($key)) {
            list($uid, $type, $symbol) = explode(',', $s);
            $this->redis->del($key);
            $this->redis->sRem("ex:$type:$symbol", $fd);
            $this->redis->sRem('ex:' . $type, $fd);
            if ($uid) $this->redis->hSet('u:' . $uid, "ex:$type:$symbol", '');;
        }
    }

    public static function sendMsg(int $fd, string $event, array|string $data, int $code = 200, string $msg = 'ok'): void
    {
        $sender = container()->get(Sender::class);
        $sender->push($fd, json_encode([
            'event' => $event,
            'code' => $code,
            'msg' => $msg,
            'data' => is_array($data) ? $data : json_decode($data),
        ], JSON_UNESCAPED_UNICODE));
    }

    public function onOpen($server, $request): void
    {
    }

    public function onRequest($request, $response): void
    {
    }

    public function paramValidator(array $data, array $rule, bool $is_login = true): string
    {
        return $this->validatorFactory->make($data, array_merge([
            'event' => 'required',
            'app_source' => 'required',
            'token' => $is_login ? 'required' : 'string'
        ], $rule))->errors()->first();
    }
}